//
// Created by tang on 2022/1/16.
//

#include "sql_driver_loader.hpp"
#include "sql/sql_driver.hpp"
#include "app/app.hpp"
#include <filesystem>

#include "utils/path_utils.hpp"

static void AddDriverFolderImpl(const char* dir);

#if defined(WIN32) || defined(_WIN32)

#include <Windows.h>

static std::wstring ToWString(const std::string& u8str)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, u8str.data(), -1, nullptr, 0);
    WCHAR* temp = new WCHAR[len + 1]{ 0 };
    MultiByteToWideChar(CP_UTF8, 0, u8str.data(), -1, temp, len);
    std::wstring ret{ temp };
    delete[] temp;
    return ret;
}

void AddDriverFolderImpl(const char* dir)
{
    // auto wpath = ToWString(dir);
    if (!std::filesystem::is_directory(dir))
        return;
    for (auto const& file : std::filesystem::directory_iterator{ dir })
    {
        if (file.is_regular_file() &&
            file.path().extension().wstring() == L".sqldriver")
        {
            // try load and resolve entry point
            DLL_DIRECTORY_COOKIE cookie = nullptr;
            const auto libPath = std::filesystem::path{ dir } / file.path().filename().replace_extension();
            if (is_directory(libPath))
            {
                auto libPathW = ToWString(libPath.string());
                cookie = AddDllDirectory(libPathW.c_str());
            }
            auto path = ToWString(file.path().string());
            if (const auto module = LoadLibraryExW(
                path.c_str(),
                NULL,

                // LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
                LOAD_LIBRARY_SEARCH_USER_DIRS |

                // LOAD_LIBRARY_SEARCH_APPLICATION_DIR |
                // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
                LOAD_LIBRARY_SEARCH_SYSTEM32
            )) {
                if (const auto entry_point_func = reinterpret_cast<PFN_DriverEntryPoint>(GetProcAddress(module, DYNAMIC_DRIVER_ENTRY_NAME)))
                {
                    if (auto const factory = entry_point_func())
                        Application::RegisterSqlDriver(factory);
                    else
                        FreeLibrary(module);
                }
                else
                    FreeLibrary(module);
            }
            if (cookie != nullptr)
                RemoveDllDirectory(cookie);
        }
    }
}

#elif defined(__unix__)
#include <fcntl.h>
#include <dlfcn.h>

void AddDriverFolderImpl(const char* dir)
{
    if (!std::filesystem::is_directory(dir))
        return;
    for (auto const& file : std::filesystem::directory_iterator{ dir })
    {
        if (file.is_regular_file())
        {
            // try load and resolve entry point
            auto module = dlopen(file.path().string().c_str(), RTLD_NOW);
            if (module) {
                auto entry_point_func = (PFN_DriverEntryPoint)dlsym(module, DYNAMIC_DRIVER_ENTRY_NAME);
                auto const factory = entry_point_func();
                if (factory)
                    Application::RegisterSqlDriver(factory);
            }
        }
    }
}

#else

// not supported

#endif

void AddDriverFolder(const char* dir) {
    AddDriverFolderImpl(dir);
}
